-
Notifications
You must be signed in to change notification settings - Fork 53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add automatic global style injection #48
base: master
Are you sure you want to change the base?
Conversation
79fd43f
to
35cf7b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making a PR 👍 Made some quick comments, need to do a more thorough review after work 🎉
85f1c6f
to
0d502b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally had the time to go through the PR in detail. It's amazing to see that you went through all scenarios and even added tests (which is rare for PRs)! Love this 🙌
Left a few comments regarding ways to simplify the API with the intention of keeping maintenance costs low as this library evolves. While tinkering with that I found a few ways to cut down on size.
Let me know what you think 👍
|
||
Attaches a Shadow DOM instance to your Custom Element. Remember that global styles won't affect the Custom Element unless you inject (see `options.injectGlobalStyles`) or somehow import them from the component itself. | ||
|
||
#### options.injectGlobalStyles |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can slim down the user-facing API surface area. My gut tells me that we can combine the solutions:
options.injectGlobalStyles = true
is the same asinjectGlobalStyles.selector = '*'
- We could eliminate
injectGlobalStyles.target
by looping overdocument.styleSheets
directly. It includes both inline style tags and sheets added via alink
element. Each sheet has a pointer to the node it was created by and we can match against that.
I'd love to start simple and remove filter
too. Whilst there are cases in theory where a query selector may not suffice, I can't come up with realistic real world examples where the selector approach falls short. Checking popular CSS-in-JS libs, they all mark their own stylesheets with a special attribute. For CSS-modules the user usually has tight control over naming the assets and we can match on that.
Since there all sheets are inherently global, unless bound to a shadow root, we can drop the Global
part of the variable name, imo.
const defaults = { | ||
target: document.head, | ||
selector: | ||
'style, link[rel="stylesheet"], link[rel="preload"][as="style"]', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strings tend to not compress well. If we switch to document.styleSheets
and match our selectors on sheet.ownerNode
, we should be able to get rid of it.
observeOptions: { childList: true, subtree: true }, | ||
}; | ||
|
||
this.styleObserver = beginInjectingGlobalStyles( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super tiny nit: The prefix begin*
somewhat implies that there is an end*
function somewhere. Seeing that this function is only used once we can inline it and save about 14B
.
options.injectGlobalStyles === true | ||
? defaults | ||
: /* eslint-disable indent */ | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do follow through with the stylesheet approach mentioned earlier we can remove this block.
) | ||
); | ||
|
||
return observeStyleChanges( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inlining this function here saves about 23B . It allows us to get rid of the callback
function wrapping and additional function call as we can just call cloneElementsToShadowRoot
.
) { | ||
return new MutationObserver((mutations, observer) => { | ||
mutations.forEach((mutation) => { | ||
const matchedElements = Array.prototype.slice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth checking: If we inline cloneElementsToShadowRoot
we can loop directly over the NodeList
, thereby skipping the additional iteration over elements and saving an array allocation in the process.
return <span>Hello world!</span>; | ||
} | ||
|
||
const styleElem = document.createElement('style'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the tests 🙌
Wow, good insights! I'll be going through these! |
I'm not entirely sure I understand the need for this to be a feature of My concern as it relates to As a thought: what about providing a list of stylesheets to inject into the shadow root? This would avoid the implicit copying issues, but still allow for selective style inheritance. |
@marvinhagemeister your thoughts on above? Before I put in any more work into the PR. We could just put a callback right after the attachShadow call, so we in the userland can use a solution like this on our own? |
Fixes #47